#include <windows.h>
#include <assert.h>
#include "SimpleWnd.h"

LRESULT CALLBACK SimpleWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int CALLBACK SimpleDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK HookThunk (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    /* Only for the first message, this is called.*/
    CWnd* pThis = _Module.GetClassPointer();
    assert (pThis);
    assert (!pThis->m_hWnd);
    pThis->m_hWnd = hwnd;
    pThis->InitThunk(SimpleWndProc);
    return SimpleWndProc((HWND)pThis, msg, wParam, lParam);
}

int CALLBACK HookDlgThunk (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    /* Only for the first message, this is called.*/
    CWnd* pThis = _Module.GetClassPointer();
    assert (pThis);
    assert (!pThis->m_hWnd);
    pThis->m_hWnd = hwnd;
    pThis->InitThunk(SimpleDlgProc, true);
    return SimpleDlgProc((HWND)pThis, msg, wParam, lParam);
}

LRESULT CALLBACK SimpleWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    /* When this is called, thunk has been installed. */
    CWnd* pThis = (CWnd*) hwnd;
    BOOL bHandled = TRUE;
    LRESULT ret = pThis->WndProc(pThis->m_hWnd, msg, wParam, lParam, bHandled);
    if (bHandled) {
        return ret;
    }
    ret = CallWindowProc((pThis->m_pfnDefault), pThis->m_hWnd, msg, wParam, lParam);
    if (msg == WM_NCDESTROY) {
        /* The last message. */
        HWND hwnd = pThis->m_hWnd;
        pThis->m_hWnd = NULL;
        pThis->OnFinalMessage(hwnd);
    }
    return ret;
}

int CALLBACK SimpleDlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    /* When this is called, thunk has been installed. */
    CWnd* pThis = (CWnd*) hwnd;
    BOOL bHandled = TRUE;
    int ret = pThis->WndProc(pThis->m_hWnd, msg, wParam, lParam, bHandled);
	if (bHandled) {
		switch (msg)
		{
		case WM_CHARTOITEM:		
		case WM_COMPAREITEM:
		case WM_CTLCOLORBTN:
		case WM_CTLCOLORDLG:
		case WM_CTLCOLOREDIT:
		case WM_CTLCOLORLISTBOX:
		case WM_CTLCOLORSCROLLBAR:
		case WM_CTLCOLORSTATIC:
		case WM_CTLCOLORMSGBOX:
		case WM_INITDIALOG:
		case WM_QUERYDRAGICON:
		case WM_VKEYTOITEM:
		    ::SetWindowLong(pThis->m_hWnd, DWL_MSGRESULT, ret);
			return TRUE;
			break;
		}
	}    
    if (msg == WM_NCDESTROY) {
        HWND hwnd = pThis->m_hWnd;
        pThis->m_hWnd = NULL;
        pThis->OnFinalMessage(hwnd);
    }
    return (bHandled ? TRUE : FALSE);
}

/* Implementation for CWndProcThunk */
void CWndProcThunk::Init(void* theProc, void* pThis)
{
	m_thunk.m_mov = 0x042444C7;  //C7 44 24 04 mov dword ptr [esp+0x04], pThis
	m_thunk.m_pThis = (DWORD)pThis;
	m_thunk.m_jmp = 0xe9;  // jmp rel
	m_thunk.m_relproc = (int)theProc - ((int)this+sizeof(WndProcThunk));
	FlushInstructionCache(GetCurrentProcess(), &m_thunk, sizeof(m_thunk));
}

/* 
 * Below are the implementation for CModule
 */

CModule::CModule()
{
    m_hInstance = NULL;
    m_pClasses = NULL;
    m_hMutex = CreateMutex(NULL, FALSE, NULL);
}

CModule::~CModule()
{
    WaitForSingleObject (m_hMutex, INFINITE);
    
    WndThisPtr* pCur = m_pClasses;
    while (pCur) {
        WndThisPtr* pNext = pCur->pNext;
        delete (pCur);
        pCur = pNext;
    }
    m_pClasses = NULL;

    ReleaseMutex(m_hMutex);
    CloseHandle(m_hMutex);
}

void CModule::Init(HINSTANCE hInst)
{
    m_hInstance = hInst;
}

HINSTANCE CModule::GetModuleInstance()
{
    return m_hInstance;
}

#define ICC_ALL_CLASSES (ICC_ANIMATE_CLASS | ICC_BAR_CLASSES | ICC_COOL_CLASSES | \
                         ICC_DATE_CLASSES | ICC_HOTKEY_CLASS | ICC_INTERNET_CLASSES | \
                         ICC_LISTVIEW_CLASSES | ICC_NATIVEFNTCTL_CLASS |\
                         ICC_PAGESCROLLER_CLASS | ICC_PROGRESS_CLASS | \
                         ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_UPDOWN_CLASS | \
                         ICC_USEREX_CLASSES | ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES | ICC_LINK_CLASS)

BOOL CModule::InitCommonControls()
{
   INITCOMMONCONTROLSEX icex;
   icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
   icex.dwICC  = ICC_ALL_CLASSES;
   return InitCommonControlsEx(&icex);
}

void CModule::AddClassPointer(CWnd* pThis)
{
    DWORD dwThreadID = ::GetCurrentThreadId();
    WndThisPtr* pData = new (WndThisPtr);
    if (!pData) {
        return;
    }
    
    pData->dwThreadID = dwThreadID;
    pData->pThis = pThis;

    assert (m_hMutex);
    WaitForSingleObject(m_hMutex, INFINITE);
    pData->pNext = m_pClasses;
    m_pClasses = pData;
    ReleaseMutex(m_hMutex);
    return;
}

CWnd* CModule::GetClassPointer()
{
    DWORD dwThreadID = ::GetCurrentThreadId();
    
    assert (m_hMutex);
    WaitForSingleObject(m_hMutex, INFINITE);

    WndThisPtr* pPrev = NULL;
    WndThisPtr* pCur = m_pClasses;
    CWnd* pThis = NULL;
    while (pCur && pCur->dwThreadID != dwThreadID) {
        pPrev = pCur;
        pCur = pCur->pNext;
    }
    if (!pCur) {
        ReleaseMutex(m_hMutex);
        return NULL;
    }
    pThis = pCur->pThis;
    if (pPrev) {
        pPrev->pNext = pCur->pNext;
    } else {
        m_pClasses = pCur->pNext;
    }
    delete (pCur);
    ReleaseMutex(m_hMutex);
    return pThis;
}



/*
 * Below are the implementation for CWnd.
 */


CWnd::CWnd()
{
    m_hWnd = NULL;
    m_pfnDefault = DefWindowProc;
}
    
CWnd::~CWnd()
{
    assert (m_hWnd == NULL);    
}

HWND CWnd::Create (HWND hWndParent, 
                const RECT& rcPos, 
                LPCTSTR szWindowName/* = NULL*/, 
                DWORD dwStyle/* = WS_CHILD | WS_VISIBLE*/, 
                DWORD dwExStyle/* = 0*/, 
                UINT nID/* = 0*/ )
{
    RegisterClassEx(this->GetWndClassInfo());
    DWORD err = GetLastError();

    _Module.AddClassPointer(this);
    HWND hwnd =  CreateWindowEx(dwExStyle, this->GetWndClassInfo()->lpszClassName, szWindowName, dwStyle, 
                    rcPos.left, rcPos.top, rcPos.right - rcPos.left, rcPos.bottom - rcPos.top,
                    hWndParent, (HMENU)nID, _Module.GetModuleInstance(), NULL);
    err = GetLastError();
    return hwnd;
}


WNDPROC CWnd::InitThunk(void* theProc, bool bIsDlg/*=false*/)
{
    assert (m_hWnd);
    m_thunk.Init(theProc, this);
    if (!bIsDlg) {
        return (WNDPROC)SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)&(m_thunk.m_thunk));
    } else {
        return (WNDPROC)SetWindowLong(m_hWnd, DWL_DLGPROC, (LONG)&(m_thunk.m_thunk));
    }
}

void CWnd::Attach(HWND hWnd)
{
    assert (::IsWindow(hWnd));
    m_pfnDefault = (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);
    DWORD err = GetLastError();
    if (!m_pfnDefault) {
        m_pfnDefault = (WNDPROC) GetClassLong(hWnd, GCL_WNDPROC);
    }

    _Module.AddClassPointer(this);
    m_hWnd = hWnd;
    InitThunk(SimpleWndProc);
}

void CWnd::OnFinalMessage(HWND hWnd)
{
    // Do nothing. User can override it to "delete this".
    // After this function, "pThis" will not be used.
    // the window that hWnd points to is already destroyed (non-existant). 
}

HWND CWnd::GetParent()
{
    assert (::IsWindow(m_hWnd));
    return ::GetParent(m_hWnd);
}

BOOL CWnd::GetClientRect(LPRECT lpRect)
{
    assert (lpRect);
    assert (::IsWindow(m_hWnd));
    return ::GetClientRect(m_hWnd, lpRect);
}

BOOL CWnd::GetWindowRect(LPRECT lpRect)
{
    assert (lpRect);
    assert (::IsWindow(m_hWnd));
    return ::GetWindowRect(m_hWnd, lpRect);
}

BOOL CWnd::ScreenToClient(LPPOINT lpPoint)
{
    assert (lpPoint);
    assert (::IsWindow(m_hWnd));
    return ::ScreenToClient(m_hWnd, lpPoint);
}

BOOL CWnd::ScreenToClient(LPRECT lpRect)
{
    assert (lpRect);
    assert (::IsWindow(m_hWnd));
    if (!::ScreenToClient(m_hWnd, (LPPOINT)lpRect) ) {
        return FALSE;
    }
    return ::ScreenToClient(m_hWnd, ((LPPOINT)lpRect)+1);
}

BOOL CWnd::ClientToScreen(LPPOINT lpPoint)
{
    assert (lpPoint);
    assert (::IsWindow(m_hWnd));
    return ::ClientToScreen(m_hWnd, lpPoint);
}

BOOL CWnd::ClientToScreen(LPRECT lpRect)
{
    assert (lpRect);
    assert (::IsWindow(m_hWnd));
    if (!::ClientToScreen(m_hWnd, (LPPOINT)lpRect) ) {
        return FALSE;
    }
    return ::ClientToScreen(m_hWnd, ((LPPOINT)lpRect)+1);
}

int CWnd::GetScrollPos(int nBar)
{
    assert (::IsWindow(m_hWnd));
    return ::GetScrollPos(m_hWnd, nBar);
}

int CWnd::SetScrollPos(int nBar, int nPos, BOOL bRedraw)
{
    assert (::IsWindow(m_hWnd));
    return ::SetScrollPos(m_hWnd, nBar, nPos, bRedraw);
}

BOOL CWnd::GetScrollRange(int nBar, LPINT lpMinPos, LPINT lpMaxPos)
{
    assert (::IsWindow(m_hWnd));
    return ::GetScrollRange (m_hWnd, nBar, lpMinPos, lpMaxPos);
}

BOOL CWnd::SetScrollRange(int nBar, int nMinPos, int nMaxPos, BOOL bRedraw)
{
    assert (::IsWindow(m_hWnd));
    return ::SetScrollRange (m_hWnd, nBar, nMinPos, nMaxPos, bRedraw);
}

BOOL CWnd::ShowScrollBar(int nBar, BOOL bShow)
{
    assert (::IsWindow(m_hWnd));
    return ::ShowScrollBar (m_hWnd, nBar, bShow);
}

BOOL CWnd::Invalidate(BOOL bErase)
{
    assert (::IsWindow(m_hWnd));
    return ::InvalidateRect(m_hWnd, NULL, bErase);
}

BOOL CWnd::InvalidateRect(LPCRECT lpRect, BOOL bErase)
{
    assert (::IsWindow(m_hWnd));
    return ::InvalidateRect(m_hWnd, lpRect, bErase);
}

BOOL CWnd::MoveWindow(int X, int Y, int nWidth, int nHeight, BOOL bRepaint)
{
    assert (::IsWindow(m_hWnd));
    return ::MoveWindow(m_hWnd, X, Y, nWidth, nHeight, bRepaint);
}

BOOL CWnd::MoveWindow(LPCRECT lpRect, BOOL bRepaint)
{
    assert (::IsWindow(m_hWnd));
    assert (lpRect);
    return ::MoveWindow(m_hWnd, 
                        lpRect->left, 
                        lpRect->top, 
                        lpRect->right - lpRect->left,
                        lpRect->bottom - lpRect->top,
                        bRepaint);
}

HDC CWnd::GetDC()
{
    assert (::IsWindow(m_hWnd));
    return ::GetDC(m_hWnd);
}

int CWnd::ReleaseDC(HDC hDC)
{
    assert (::IsWindow(m_hWnd));
    return ::ReleaseDC(m_hWnd, hDC);
}

HDC CWnd::BeginPaint(LPPAINTSTRUCT lpPaint)
{
    assert (::IsWindow(m_hWnd));
    return ::BeginPaint(m_hWnd, lpPaint);
}

BOOL CWnd::EndPaint(CONST PAINTSTRUCT *lpcPaint)
{
    assert (::IsWindow(m_hWnd));
    return ::EndPaint(m_hWnd, lpcPaint);
}

HWND CWnd::SetFocus()
{
    assert (::IsWindow(m_hWnd));
    return ::SetFocus(m_hWnd);
}

BOOL CWnd::IsWindow()
{
    return ::IsWindow(m_hWnd);
}

BOOL CWnd::HideCaret()
{
    assert (::IsWindow(m_hWnd));
    return ::HideCaret(m_hWnd);
}

BOOL CWnd::ShowCaret()
{
    assert (::IsWindow(m_hWnd));
    return ::ShowCaret(m_hWnd);
}

BOOL CWnd::CreateSolidCaret(int nWidth, int nHeight)
{
    assert (::IsWindow(m_hWnd));
    return ::CreateCaret(m_hWnd, NULL, nWidth, nHeight);
}

BOOL CWnd::CreateCaret(HBITMAP hBitmap, int nWidth, int nHeight)
{
    assert (::IsWindow(m_hWnd));
    return ::CreateCaret(m_hWnd, hBitmap, nWidth, nHeight);
}

HWND CWnd::SetCapture()
{
    assert (::IsWindow(m_hWnd));
    return ::SetCapture(m_hWnd);
}

BOOL CWnd::OpenClipboard()
{
    assert (::IsWindow(m_hWnd));
    return ::OpenClipboard(m_hWnd);
}

BOOL CWnd::UpdateWindow()
{
    assert (::IsWindow(m_hWnd));
    return ::UpdateWindow(m_hWnd);
}

BOOL CWnd::ShowWindow(int nCmdShow)
{
    assert (::IsWindow(m_hWnd));
    return ::ShowWindow(m_hWnd, nCmdShow);
}

BOOL CWnd::DestroyWindow()
{
    assert (::IsWindow(m_hWnd));
    return ::DestroyWindow(m_hWnd);
}

BOOL CWnd::CenterWindow(HWND hWndCenter)
{
	assert(::IsWindow(m_hWnd));

	// determine owner window to center against
	DWORD dwStyle = (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
	if(hWndCenter == NULL)
	{
		if(dwStyle & WS_CHILD)
			hWndCenter = ::GetParent(m_hWnd);
		else
			hWndCenter = ::GetWindow(m_hWnd, GW_OWNER);
	}

	// get coordinates of the window relative to its parent
	RECT rcDlg;
	::GetWindowRect(m_hWnd, &rcDlg);
	RECT rcArea;
	RECT rcCenter;
	HWND hWndParent;
	if(!(dwStyle & WS_CHILD))
	{
		// don't center against invisible or minimized windows
		if(hWndCenter != NULL)
		{
			DWORD dwStyle = ::GetWindowLong(hWndCenter, GWL_STYLE);
			if(!(dwStyle & WS_VISIBLE) || (dwStyle & WS_MINIMIZE))
				hWndCenter = NULL;
		}

		// center within screen coordinates
		::SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcArea, NULL);
		if(hWndCenter == NULL)
			rcCenter = rcArea;
		else
			::GetWindowRect(hWndCenter, &rcCenter);
	}
	else
	{
		// center within parent client coordinates
		hWndParent = ::GetParent(m_hWnd);
		assert(::IsWindow(hWndParent));

		::GetClientRect(hWndParent, &rcArea);
		assert(::IsWindow(hWndCenter));
		::GetClientRect(hWndCenter, &rcCenter);
		::MapWindowPoints(hWndCenter, hWndParent, (POINT*)&rcCenter, 2);
	}

	int DlgWidth = rcDlg.right - rcDlg.left;
	int DlgHeight = rcDlg.bottom - rcDlg.top;

	// find dialog's upper left based on rcCenter
	int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
	int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;

	// if the dialog is outside the screen, move it inside
	if(xLeft < rcArea.left)
		xLeft = rcArea.left;
	else if(xLeft + DlgWidth > rcArea.right)
		xLeft = rcArea.right - DlgWidth;

	if(yTop < rcArea.top)
		yTop = rcArea.top;
	else if(yTop + DlgHeight > rcArea.bottom)
		yTop = rcArea.bottom - DlgHeight;

	// map screen coordinates to child coordinates
	return ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1,
		SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}


/****************************************************************
 * Below is the implementation of CDialog
 */

CDialog::CDialog() {};
CDialog::~CDialog() {};

HWND CDialog::Create( HWND hWndParent, LPARAM dwInitParam)
{
    m_pfnDefault = NULL;

    _Module.AddClassPointer(this);
    HWND hwnd =  ::CreateDialogParam(_Module.GetModuleInstance(), 
                                MAKEINTRESOURCE(this->GetDialogID()), 
                                hWndParent,
                                HookDlgThunk,
                                dwInitParam);
    return hwnd;
}

int CDialog::DoModal( HWND hWndParent)
{
    m_pfnDefault = NULL;

    _Module.AddClassPointer(this);
    DWORD id = this->GetDialogID();
    int ret =  ::DialogBox(_Module.GetModuleInstance(), 
                                MAKEINTRESOURCE(this->GetDialogID()), 
                                hWndParent,
                                HookDlgThunk);
    return ret;
}

HWND CDialog::GetDlgItem(int nIDDlgItem)
{
    assert (::IsWindow(m_hWnd));
    return ::GetDlgItem(m_hWnd, nIDDlgItem);
}

BOOL CDialog::EndDialog(int nResult)
{
    assert (::IsWindow(m_hWnd));
    return ::EndDialog(m_hWnd, nResult);
}

BOOL CDialog::CheckRadioButton(int nIDFirstButton, int nIDLastButton,  int nIDCheckButton)
{
	assert(::IsWindow(m_hWnd));
    return ::CheckRadioButton(m_hWnd, nIDFirstButton, nIDLastButton, nIDCheckButton);
}

void CDialog::NextDlgCtrl()
{
	assert(::IsWindow(m_hWnd));
	::SendMessage(m_hWnd, WM_NEXTDLGCTL, 0, 0L);
}

void CDialog::PrevDlgCtrl()
{
	assert(::IsWindow(m_hWnd));
	::SendMessage(m_hWnd, WM_NEXTDLGCTL, 1, 0L);
}

UINT CDialog::IsDlgButtonChecked(int nIDButton)
{
	assert(::IsWindow(m_hWnd));
    return ::IsDlgButtonChecked(m_hWnd, nIDButton);
}

#ifdef DEBUG
#define TRACE_LEN 1024
void MyTrace(LPCTSTR format, ...)
{
   TCHAR buf[TRACE_LEN];
   buf[TRACE_LEN-1] = _T('\0');
   va_list  arglist;
   va_start (arglist, format);
   _vsntprintf(buf, TRACE_LEN, format, arglist);
   va_end (arglist);
   OutputDebugString(buf);
}
#endif
